using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;

namespace Is.Core
{
    internal class Is
    {
        
        protected Is() { }

        public static bool Contains<T>(IEnumerable<T> collection, T expected)
        {
            return Contains(collection, expected, GetEqualityComparer<T>());
        }

        public static bool Contains<T>(IEnumerable<T> collection, T expected, IEqualityComparer<T> comparer)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (comparer == null) throw new ArgumentNullException("comparer");

            foreach (T item in collection)
                if (comparer.Equals(expected, item))
                    return true;

            return false;
        }

        public static bool ContainsExactly<T>(IEnumerable<T> collection, T expected, int expectedCount)
        {
            return ContainsExactly(collection, expected, GetEqualityComparer<T>(), expectedCount);
        }

        public static bool ContainsExactly<T>(IEnumerable<T> collection, T expected, IEqualityComparer<T> comparer, int expectedCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (comparer == null) throw new ArgumentNullException("comparer");
            int count = 0;
            foreach (T item in collection)
            {
                if (comparer.Equals(expected, item))
                {
                    count++;
                    if (count > expectedCount)
                        return false;
                }
            }

            return count == expectedCount;
        }

        public static bool ContainsAtLeast<T>(IEnumerable<T> collection, T expected, int expectedMinimumCount)
        {
            return ContainsExactly(collection, expected, GetEqualityComparer<T>(), expectedMinimumCount);
        }

        public static bool ContainsAtLeast<T>(IEnumerable<T> collection, T expected, IEqualityComparer<T> comparer, int expectedMinimumCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (comparer == null) throw new ArgumentNullException("comparer");
            int count = 0;
            foreach (T item in collection)
            {
                if (comparer.Equals(expected, item))
                {
                    count++;
                    if (count >= expectedMinimumCount)
                        return true;
                }
            }
            return count == expectedMinimumCount;
        }

        public static bool ContainsNoMoreThan<T>(IEnumerable<T> collection, T expected, int expectedMaximumCount)
        {
            return ContainsExactly(collection, expected, GetEqualityComparer<T>(), expectedMaximumCount);
        }

        public static bool ContainsNoMoreThan<T>(IEnumerable<T> collection, T expected, IEqualityComparer<T> comparer, int expectedMaximumCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (comparer == null) throw new ArgumentNullException("comparer");
            int count = 0;
            foreach (T item in collection)
            {
                if (comparer.Equals(expected, item))
                {
                    count++;
                    if (count > expectedMaximumCount)
                        return false;
                }
            }
            return true;
        }

        public static bool Contains<T>(IEnumerable<T> collection, Func<T, bool> predicate)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (predicate == null) throw new ArgumentNullException("predicate");

            //// TODO ?performace
            //return collection.Any(predicate);
            
            foreach (T item in collection)
            {
                if (predicate.Invoke(item))
                {
                    return true;
                }
            }
            return false;
        }

        //// TODO Not version is good or not? if yes, do it everywhere..
        public static bool NotContains<T>(IEnumerable<T> collection, Func<T, bool> predicate)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (predicate == null) throw new ArgumentNullException("predicate");

            //// TODO ?performace
            //return collection.Any(predicate);

            foreach (T item in collection)
            {
                if (predicate.Invoke(item))
                {
                    return false;
                }
            }
            return true;
        }

        public static bool ContainsExactly<T>(IEnumerable<T> collection, Func<T, bool> predicate, int expectedCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (predicate == null) throw new ArgumentNullException("predicate");

            //// TODO ?performace
            //return collection.Count(predicate) == expectedCount;

            int count = 0;
            foreach (T item in collection)
            {
                if (predicate.Invoke(item))
                {
                    count++;
                    if (count > expectedCount)
                        return false;
                }
            }
            return count == expectedCount;
        }

        public static bool ContainsAtLeast<T>(IEnumerable<T> collection, Func<T, bool> predicate, int expectedMinimumCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (predicate == null) throw new ArgumentNullException("predicate");

            //// TODO ?performace
            //return collection.Count(predicate) == expectedCount;

            int count = 0;
            foreach (T item in collection)
            {
                if (predicate.Invoke(item))
                {
                    count++;
                    if (count >= expectedMinimumCount)
                        return true;
                }
            }
            return count == expectedMinimumCount;
        }

        public static bool ContainsNoMoreThan<T>(IEnumerable<T> collection, Func<T, bool> predicate, int expectedMaximumCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (predicate == null) throw new ArgumentNullException("predicate");

            //// TODO ?performace
            //return collection.Count(predicate) == expectedCount;

            int count = 0;
            foreach (T item in collection)
            {
                if (predicate.Invoke(item))
                {
                    count++;
                    if (count > expectedMaximumCount)
                        return false;
                }
            }
            return true;
        }

        public static bool ContainsExactly(IEnumerable collection, int expectedCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            
            //// TODO ?performace
            //return collection.Count(predicate) == expectedCount;

            int count = 0;
            foreach (var item in collection)
            {
                count++;
                if (count > expectedCount)
                    return false;
            }
            return count == expectedCount;
        }

        public static bool ContainsAtLeast(IEnumerable collection, int expectedMinimumCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            
            //// TODO ?performace
            //return collection.Count(predicate) == expectedCount;
            int count = 0;
            foreach (var item in collection)
            {
                count++;
                if (count >= expectedMinimumCount)
                    return true;
            }
            return count == expectedMinimumCount;
        }

        public static bool ContainsNoMoreThan(IEnumerable collection, int expectedMaximumCount)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            
            //// TODO ?performace
            //return collection.Count(predicate) == expectedCount;

            int count = 0;
            foreach (var item in collection)
            {
                count++;
                if (count > expectedMaximumCount)
                    return false;
            }
            return true;
        }

        public static bool Contains(string actualString, string expectedSubString)
        {
            return Contains(actualString, expectedSubString, StringComparison.CurrentCulture);
        }

        public static bool Contains(string actualString, string expectedSubString, StringComparison comparisonType)
        {
            if (actualString == null) throw new ArgumentNullException("actualString");
            if (expectedSubString == null) throw new ArgumentNullException("expectedSubString");
            if (expectedSubString == string.Empty)
            {
                return actualString == string.Empty;
            }
            int indexOf = actualString.IndexOf(expectedSubString, comparisonType);

            if (indexOf < 0)
                return false;

            return true;
        }

        public static bool StartsWith(string actualString, string expectedStartString)
        {
            if (actualString == null) throw new ArgumentNullException("actualString");
            if (expectedStartString == null) throw new ArgumentNullException("expectedStartString");
            if (expectedStartString == string.Empty)
            {
                return actualString == string.Empty;
            }
            return actualString.StartsWith(expectedStartString);
        }

        public static bool EndsWith(string actualString, string expectedEndString)
        {
            if (actualString == null) throw new ArgumentNullException("actualString");
            if (expectedEndString == null) throw new ArgumentNullException("expectedEndString");
            if (expectedEndString == string.Empty)
            {
                return actualString == string.Empty;
            }
            return actualString.EndsWith(expectedEndString);
        }

        public static bool NotContains<T>(IEnumerable<T> collection, T expected)
        {
            return NotContains(collection, expected, GetEqualityComparer<T>());
        }

        public static bool NotContains<T>(IEnumerable<T> collection, T expected, IEqualityComparer<T> comparer)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");
            if (comparer == null) throw new ArgumentNullException("comparer");
            
            foreach (T item in collection)
                if (comparer.Equals(expected, item))
                    return false;

            return true;
        }

        public static bool NotContains(string actualString, string expectedSubString)
        {
            if (expectedSubString == string.Empty)
            {
                return actualString != string.Empty;
            }
            return NotContains(actualString, expectedSubString, StringComparison.CurrentCulture);
        }

        public static bool NotContains(string actualString, string expectedSubString, StringComparison comparisonType)
        {
            if (actualString == null) throw new ArgumentNullException("actualString");
            if (expectedSubString == null) throw new ArgumentNullException("expectedSubString");

            if (actualString.IndexOf(expectedSubString, comparisonType) >= 0)
                return false;
            return true;
        }

        public static bool Empty(IEnumerable collection)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");

            foreach (object @object in collection)
                return false;

            return true;
        }

        public static bool Equal<T>(T actual, T expected)
        {
            return Equal(actual, expected, GetEqualityComparer<T>());
        }

        public static bool Equal<T>(T actual, T expected, IEqualityComparer<T> comparer)
        {
            if (comparer == null) throw new ArgumentNullException("comparer");
            return comparer.Equals(actual, expected);
        }

        public static bool Equal(double actual, double expected, double tolerance)
        {
            var difference = Math.Abs(actual - expected);
            if (difference > tolerance)
                return false;
            return true;
        }


        public static bool NotEqual(double actual, double expected, double tolerance)
        {
            var difference = Math.Abs(actual - expected);
            if (difference <= tolerance)
                return false;
            return true;
        }

        public static bool Equal(DateTime actual, DateTime expected, TimeSpan tolerance)
        {
            var difference = Math.Abs((actual - expected).Ticks);
            if (difference > tolerance.Ticks)
                return false;
            return true;
        }

        public static bool NotEqual(DateTime actual, DateTime expected, TimeSpan tolerance)
        {
            var difference = Math.Abs((actual - expected).Ticks);
            if (difference <= tolerance.Ticks)
                return false;
            return true;
        }

        public static bool Equal(DateTime actual, DateTime expected, DatePrecision precision)
        {
            if (precision.Truncate(expected) != precision.Truncate(actual))
                return false;
            return true;
        }

        public static bool NotEqual(DateTime actual, DateTime expected, DatePrecision precision)
        {
            if (precision.Truncate(expected) == precision.Truncate(actual))
                return false;
            return true;
        }

        /// <summary>Do not call this method.</summary>
        [Obsolete("This is an override of Object.Equals(). Call Equal() instead.", true)]
        public new static bool Equals(object a, object b)
        {
            throw new InvalidOperationException("Equals should not be used");
        }

        public static bool False(bool condition)
        {
            return condition == false;
        }

        static IEqualityComparer<T> GetEqualityComparer<T>()
        {
            return new IsEqualityComparer<T>();
        }

        static IComparer<T> GetComparer<T>()
        {
            return new IsComparer<T>();
        }

        public static bool GreaterThan<T>(T left, T right)
        {
            return GreaterThan(left, right, GetComparer<T>());
        }

        public static bool GreaterThan<T>(T left, T right, IComparer<T> comparer)
        {
            if (IsNullable<T>())
            {
                if (left == null) throw new ArgumentNullException("left");
                if (right == null) throw new ArgumentNullException("right");
            }
            if (comparer == null) throw new ArgumentNullException("comparer");
            if (comparer.Compare(left, right) <= 0)
                return false;
            return true;
        }

        public static bool GreaterThanOrEqual<T>(T left, T right)
        {
            return GreaterThanOrEqual(left, right, GetComparer<T>());
        }

        public static bool GreaterThanOrEqual<T>(T left, T right, IComparer<T> comparer)
        {
            if (IsNullable<T>())
            {
                if (left == null) throw new ArgumentNullException("left");
                if (right == null) throw new ArgumentNullException("right");
            }
            if (comparer == null) throw new ArgumentNullException("comparer");
            if (comparer.Compare(left, right) < 0)
                return false;
            return true;
        }

        public static bool InRange<T>(T actual, T low, T high)
        {
            return InRange(actual, low, high, GetComparer<T>());
        }

        public static bool InRange<T>(T actual, T low, T high, IComparer<T> comparer)
        {
            if (IsNullable<T>())
            {
                if (actual == null) throw new ArgumentNullException("actual");
                if (low == null) throw new ArgumentNullException("low");
                if (high == null) throw new ArgumentNullException("high");
            }
            if (comparer == null) throw new ArgumentNullException("comparer");
            if (comparer.Compare(low, actual) > 0 || comparer.Compare(actual, high) > 0)
                return false;
            return true;
        }

        public static bool AssignableFrom<T>(object @object)
        {
            return AssignableFrom(@object, typeof(T));
        }

        public static bool AssignableFrom(object @object, Type expectedType)
        {
            if (expectedType == null) throw new ArgumentNullException("expectedType");
            if (@object == null || !expectedType.IsAssignableFrom(@object.GetType()))
                return false;
            return true;
        }

        public static bool IsType<T>(object @object)
        {
            return IsType(@object, typeof(T));
        }

        public static bool IsType(object @object, Type expectedType)
        {
            if (expectedType == null) throw new ArgumentNullException("expectedType");
            if (@object == null || !expectedType.Equals(@object.GetType()))
                return false;
            return true;
        }

        public static bool LessThan<T>(T left, T right)
        {
            return LessThan(left, right, GetComparer<T>());
        }

        public static bool LessThan<T>(T left, T right, IComparer<T> comparer)
        {
            if (IsNullable<T>())
            {
                if (left == null) throw new ArgumentNullException("left");
                if (right == null) throw new ArgumentNullException("right");
            }
            if (comparer == null) throw new ArgumentNullException("comparer");
            if (comparer.Compare(left, right) >= 0)
                return false;
            return true;
        }

        public static bool LessThanOrEqual<T>(T left, T right)
        {
            return LessThanOrEqual(left, right, GetComparer<T>());
        }

        public static bool LessThanOrEqual<T>(T left, T right, IComparer<T> comparer)
        {
            if (IsNullable<T>())
            {
                if (left == null) throw new ArgumentNullException("left");
                if (right == null) throw new ArgumentNullException("right");
            }
            if (comparer == null) throw new ArgumentNullException("comparer");
            if (comparer.Compare(left, right) > 0)
                return false;
            return true;
        }

        public static bool NotEmpty(IEnumerable collection)
        {
            if (collection == null) throw new ArgumentNullException("collection", "cannot be null");

            foreach (object @object in collection)
                return true;

            return false;
        }

        public static bool NotEqual<T>(T actual, T expected)
        {
            return NotEqual(actual, expected, GetEqualityComparer<T>());
        }

        public static bool NotEqual<T>(T actual, T expected, IEqualityComparer<T> comparer)
        {
            if (comparer == null) throw new ArgumentNullException("comparer");
            if (comparer.Equals(expected, actual))
                return false;
            return true;
        }

        public static bool NotInRange<T>(T actual, T low, T high)
        {
            return NotInRange(actual, low, high, GetComparer<T>());
        }

        public static bool NotInRange<T>(T actual, T low, T high, IComparer<T> comparer)
        {
            if (IsNullable<T>())
            {
                if (actual == null) throw new ArgumentNullException("actual");
                if (low == null) throw new ArgumentNullException("low");
                if (high == null) throw new ArgumentNullException("high");
            }
            if (comparer == null) throw new ArgumentNullException("comparer");
            if (comparer.Compare(low, actual) <= 0 && comparer.Compare(actual, high) <= 0)
                return false;
            return true;
        }

        public static bool NotNull(object @object)
        {
            return (@object != null);
        }

        public static bool NotSame(object actual, object expected)
        {
            if (object.ReferenceEquals(expected, actual))
                return false;
            return true;
        }

        public static bool Null(object @object)
        {
            return (@object == null);
        }

        public static bool Same(object actual, object expected)
        {
            return object.ReferenceEquals(expected, actual);
        }

        public static bool True(bool condition)
        {
            return condition;
        }

        public static bool IsNullable<T>(T obj)
        {
            if (obj == null) return true; // obvious
            Type type = typeof(T);
            return IsNullable(type);
        }

        public static bool IsNullable<T>()
        {
            Type type = typeof(T);
            return IsNullable(type);
        }

        public static bool IsNullable(Type type)
        {
            if (!type.IsValueType) return true; // ref-type
            if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
            return false; // value-type
        }


        /// <summary>
        /// Does not check recursively the predicate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="actual"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static bool IsExist<T>(T actual, Expression predicate)
        {
            if (predicate == null) throw new ArgumentNullException("predicate");

            if (Null(actual))
                return false;

            var parameterExpressionVisitor = new ParameterExpressionVisitor();
            var parameters = parameterExpressionVisitor.GetParameters(predicate);
            Expression convertToObjectExpression = Expression.Convert(predicate, typeof(object));
            var newLambda = Expression.Lambda<Func<T, object>>(convertToObjectExpression, parameters);
            var compiledLambda = newLambda.Compile();
            var valueOfPredicate = compiledLambda.Invoke(actual);
            return NotNull(valueOfPredicate);
        }

        /// <summary>
        /// Recursively the predicate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="actual"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static bool HasExisting<T>(T actual, Expression<Func<T, object>> predicate)
        {
            if (predicate == null) throw new ArgumentNullException("predicate");

            if (Null(actual))
                return false;

            var subExpressions = predicate.GetExpressions();
            foreach (var subExpression in subExpressions)
            {
                var subExpressionIsExist = IsExist(actual, subExpression);
                if (!subExpressionIsExist)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Does not check recursively the predicate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="K"></typeparam>
        /// <param name="actual"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static K Value<T, K>(T actual, Expression predicate)
        {
            if (predicate == null) throw new ArgumentNullException("predicate");
            if (Null(actual)) throw new ArgumentNullException("actual");

            var parameterExpressionVisitor = new ParameterExpressionVisitor();
            var parameters = parameterExpressionVisitor.GetParameters(predicate);
            Expression convertToObjectExpression = Expression.Convert(predicate, typeof(K));
            var newLambda = Expression.Lambda<Func<T, K>>(convertToObjectExpression, parameters);
            var compiledLambda = newLambda.Compile();
            var valueOfPredicate = compiledLambda.Invoke(actual);
            return valueOfPredicate;
        }

        /// <summary>
        /// Recursively the predicate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="K"></typeparam>
        /// <param name="actual"></param>
        /// <param name="predicate"></param>
        /// <param name="expectedValue"></param>
        /// <returns></returns>
        public static bool HasExistingWithValue<T, K>(T actual, Expression<Func<T, K>> predicate, K expectedValue)
        {
            if (predicate == null) throw new ArgumentNullException("predicate");

            Expression expressionsforValue = predicate.Body;
            var subExpressions = predicate.GetExpressions().ToArray();
            if (subExpressions.Length > 0)
            {
                if (Null(actual))
                    return false;
                for (int i = 0; i < subExpressions.Length - 1; i++)
                {
                    var subExpression = subExpressions[i];
                    var subExpressionIsExist = IsExist(actual, subExpression);
                    if (!subExpressionIsExist)
                    {
                        return false;
                    }
                }
                expressionsforValue = subExpressions[subExpressions.Length - 1];
            }
            var actualValue = Value<T, K>(actual, expressionsforValue);
            return Equal(expectedValue, actualValue);
        }

        public static bool HasExistingWithValue<T>(T actual, Expression<Func<T, T>> predicate, T expectedValue)
        {
            if (predicate == null) throw new ArgumentNullException("predicate");

            Expression expressionsforValue = predicate.Body;
            var subExpressions = predicate.GetExpressions().ToArray();
            T actualValue;
            if (subExpressions.Length > 0)
            {
                if (Null(actual))
                    return false;
                for (int i = 0; i < subExpressions.Length - 1; i++)
                {
                    var subExpression = subExpressions[i];
                    var subExpressionIsExist = IsExist(actual, subExpression);
                    if (!subExpressionIsExist)
                    {
                        return false;
                    }
                }
                expressionsforValue = subExpressions[subExpressions.Length - 1];
                actualValue = Value<T, T>(actual, expressionsforValue);
            }
            else
            {
                actualValue = actual;
            }
            return Equal(expectedValue, actualValue);
        }

        /// <summary>
        /// Recursively the predicate
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="K"></typeparam>
        /// <param name="actual"></param>
        /// <param name="predicate"></param>
        /// <param name="expectedValue"></param>
        /// <param name="comparer"></param>
        /// <returns></returns>
        public static bool HasExistingWithValue<T, K>(T actual, Expression<Func<T, K>> predicate, K expectedValue, IEqualityComparer<K> comparer)
        {
            if (predicate == null) throw new ArgumentNullException("predicate");

            Expression expressionsforValue = predicate.Body;
            var subExpressions = predicate.GetExpressions().ToArray();
            if (subExpressions.Length > 0)
            {
                if (Null(actual))
                    return false;
                for (int i = 0; i < subExpressions.Length - 1; i++)
                {
                    var subExpression = subExpressions[i];
                    var subExpressionIsExist = IsExist(actual, subExpression);
                    if (!subExpressionIsExist)
                    {
                        return false;
                    }
                }
                expressionsforValue = subExpressions[subExpressions.Length - 1];
            }
            var actualValue = Value<T, K>(actual, expressionsforValue);
            return Equal(expectedValue, actualValue, comparer);
        }

        public static bool ConvertableTo<T, K>(T convertFrom)
        {
            try
            {
                var converter = TypeDescriptor.GetConverter(typeof(K));
                if (converter == null)
                    return false;
                var converted = (K)converter.ConvertFrom(convertFrom);
            }
            catch (Exception)
            {
                return false;
            }
            return true;

        }
    }
}